/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.web.impl.thread;

import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.paoding.rose.Rc;
import net.paoding.rose.web.InvocationBean;
import net.paoding.rose.web.annotation.HttpFeatures;
import net.paoding.rose.web.annotation.Ignored;
import net.paoding.rose.web.annotation.Intercepted;
import net.paoding.rose.web.annotation.Param;
import net.paoding.rose.web.annotation.ReqMapping;
import net.paoding.rose.web.annotation.ReqMethod;
import net.paoding.rose.web.annotation.Return;
import net.paoding.rose.web.impl.module.Module;
import net.paoding.rose.web.impl.module.NamedControllerInterceptor;
import net.paoding.rose.web.impl.validation.ParameterBindingResult;
import net.paoding.rose.web.instruction.View;
import net.paoding.rose.web.var.PrivateVar;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.util.WebUtils;

/**
 * 
 * @author qieqie.wang@paoding.net
 * 
 */
public class ActionInvocation implements WebInvocation {
	private static Log logger = LogFactory.getLog(ActionInvocation.class);
	private ControllerInvocation controllerInvocation;
	private Method method;
	private String requestMethods = "*";
	private NamedControllerInterceptor[] registeredInterceptors;
	private MethodParameterResolver methodParameterResolver;
	private String[] parameterNames;
	private Param[] paramAnnotations;
	private boolean hasMultipartParams;
	private transient String toStringCached;
	

	public ActionInvocation(ControllerInvocation controllerInvocation,
			Method method) {
		this.controllerInvocation = controllerInvocation;
		this.method = method;
		init();
	}

	private String methodToString() {
		return method.toString();
	}

	public ControllerInvocation getControllerInvocation() {
		return controllerInvocation;
	}

	public NamedControllerInterceptor[] getRegisteredInterceptors() {
		return registeredInterceptors;
	}

	public Method getMethod() {
		return method;
	}

	public String getRequestMethods() {
		return requestMethods;
	}

	public boolean isRequestMethodSupported(String requestMethod) {
		if (!requestMethods.equals("*")) {
			requestMethod = requestMethod.toUpperCase();
			if (requestMethods.indexOf(requestMethod) == -1) {
				return false;
			}
		}
		return true;
	}

	public String[] getParameterNames() {
		return parameterNames;
	}

	private void init() {
		//
		if (method.getAnnotation(ReqMapping.class) != null) {
			ReqMapping path = method.getAnnotation(ReqMapping.class);
			ReqMethod[] allow = path.methods();
			if (ArrayUtils.contains(allow, ReqMethod.ALL)) {
				requestMethods = "*";
			} else if (allow.length > 0) {
				requestMethods = "";
				for (int i = 0; i < allow.length; i++) {
					if (i == 0) {
						requestMethods = allow[i].toString();
					} else {
						requestMethods += "," + allow[i].toString();
					}
				}
			}
		}
		// intercetors: 方法有没有? 类有没有? 父类有没有?
		Intercepted intercepted = method.getAnnotation(Intercepted.class);
		if (intercepted == null) {
			Class<?> clazz = controllerInvocation.getControllerClass();
			while (intercepted == null) {
				intercepted = clazz.getAnnotation(Intercepted.class);
				clazz = clazz.getSuperclass();
				if (clazz == null || clazz.equals(Object.class)
						|| clazz.getAnnotation(Ignored.class) != null) {
					break;
				}
			}
		}
		Module module = controllerInvocation.getModule();
		NamedControllerInterceptor[] interceptors = module.getInterceptors();
		ArrayList<NamedControllerInterceptor> registeredInterceptors = new ArrayList<NamedControllerInterceptor>(
				interceptors.length);
		for (int i = 0; i < interceptors.length; i++) {
			if (interceptors[i].getController() != null) {
				// 1、controller自己声明的拦截器，只作用自己
				if (controllerInvocation.getController() != interceptors[i]
						.getController()) {
					continue;
				}
			}

			// 2、如果拦截器定义必须标注某些annoation才要拦截的...
			// 那么只有标注了这些annotation的才拦截，不标注的不拦截
			String name = interceptors[i].getName();
			String nameForUser = name;
			if (nameForUser.indexOf('.') != -1) {
				nameForUser = nameForUser.replace(controllerInvocation
						.getControllerPath()
						+ ".", "this.");
			}
			Class<? extends Annotation>[] interceptorAnnotationRequired = interceptors[i]
					.getAnnotationClasses();
			if (interceptorAnnotationRequired != null
					&& interceptorAnnotationRequired.length > 0) {
				for (Class<? extends Annotation> annotationClazz : interceptorAnnotationRequired) {
					if (annotationClazz == null) {
						continue;
					}
					Annotation interceptorRequiredTypeAnnotation = null;
					Annotation interceptorRequiredMethodAnnotation = method
							.getAnnotation(annotationClazz);
					Class<?> temp = controllerInvocation.getControllerClass();
					while (interceptorRequiredMethodAnnotation == null
							&& interceptorRequiredTypeAnnotation == null) {
						interceptorRequiredTypeAnnotation = temp
								.getAnnotation(annotationClazz);
						temp = temp.getSuperclass();
						if (temp == null || temp.equals(Object.class)
								|| temp.getAnnotation(Ignored.class) != null) {
							break;
						}
					}
					if (interceptorRequiredTypeAnnotation == null
							&& interceptorRequiredMethodAnnotation == null) {
						// don't intercept it
					} else {
						registeredInterceptors.add(interceptors[i]);
						if (logger.isDebugEnabled()) {
							logger.debug("register interceptor"
									+ " (by intercetor annotation) '" + name
									+ "' for " + methodToString());
						}
						break;
					}
				}
				// continue
				continue;
			}
			// 3、拦截器没有要求特有的annotation的，这种情况通过@Intercepted来标注

			// 没有标注@Intercepted注解的，所有拦截器都能拦截
			if (intercepted == null) {
				if (interceptors[i].isApplicationContextInterceptor()) {
					registeredInterceptors.add(interceptors[i]);
					if (logger.isDebugEnabled()) {
						logger.debug("register global interceptor '" + name
								+ "' for " + methodToString());
					}
				} else {
					registeredInterceptors.add(interceptors[i]);
					if (logger.isDebugEnabled()) {
						logger.debug("register interceptor '" + name + "' for "
								+ methodToString());
					}
				}
			}
			// 标注@Intercepted来标注注解的
			else {
				// 3.1先排除deny禁止的
				if (ArrayUtils.contains(intercepted.deny(), "*")
						|| ArrayUtils.contains(intercepted.deny(), nameForUser)) {
					if (logger.isDebugEnabled()) {
						logger.debug("(1)igonre interceptor '" + nameForUser
								+ "' for " + methodToString());
					}
					continue;
				}
				// 3.2确认最大的allow允许
				if (intercepted.allow().length > 0) {
					if (ArrayUtils.contains(intercepted.allow(), "*")
							|| ArrayUtils.contains(intercepted.allow(),
									nameForUser)) {
						registeredInterceptors.add(interceptors[i]);
						if (logger.isDebugEnabled()) {
							logger.debug("register interceptor '" + name
									+ "' for " + "." + methodToString());
						}
					} else {
						if (logger.isDebugEnabled()) {
							logger.debug("(2)igonre interceptor '" + name
									+ "' for " + methodToString());
						}
					}
				}
			}
		}
		//
		this.registeredInterceptors = registeredInterceptors
				.toArray(new NamedControllerInterceptor[registeredInterceptors
						.size()]);
		//
		methodParameterResolver = new MethodParameterResolver(this);
		this.hasMultipartParams = methodParameterResolver.hasMultipartParams();
		//
		ParameterNameDiscovererImpl parameterNameDiscoverer = new ParameterNameDiscovererImpl();

		parameterNames = parameterNameDiscoverer.getParameterNames(method);
		this.paramAnnotations = parameterNameDiscoverer
				.getParamAnnotations(method);
	}

	public Object invoke(HttpServletRequest request,
			HttpServletResponse response) throws Throwable {
		if (!isRequestMethodSupported(request.getMethod())) {
			Path path = (Path) PrivateVar.invocationVariables().getPath();
			return "e:405;Request method '" + request.getMethod()
					+ "' not supported for '" + path.getUri() + "'";
		}

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			logger.debug("Taking snapshot of request"
					+ " attributes before include");
			attributesSnapshot = new HashMap<String, Object>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				attributesSnapshot
						.put(attrName, request.getAttribute(attrName));
			}
		}
		PrivateVar.invocationVariables().setAttributesSnapshot(
				attributesSnapshot);

		HttpServletRequest processedRequest = request;
		try {
			if (hasMultipartParams) {
				processedRequest = checkMultipart(request);
				if (processedRequest != request) {
					PrivateVar.request(processedRequest);
				}
			}
			//
			applyHttpFeatures(processedRequest, response);

			InvocationBean invocation = new InvocationBean(this);
			Object[] parameters = new Object[parameterNames.length];
			invocation.setMethodParameters(parameters);
			PrivateVar.invocationVariables().setInvocation(invocation);

			ParameterBindingResult parameterBindingResult = new ParameterBindingResult(
					invocation);
			Rc.model().add(
					BindingResult.MODEL_KEY_PREFIX
							+ parameterBindingResult.getObjectName(),
					parameterBindingResult);
			methodParameterResolver.resolve(processedRequest, parameters);

			// add to model
			for (int i = 0; i < parameterNames.length; i++) {
				if (parameterNames[i] != null && parameters[i] != null
						&& Rc.model().get(parameterNames[i]) != parameters[i]) {
					// 将bean设置到model中
					Rc.model().add(parameterNames[i], parameters[i]);
				}
			}

			// interceptors
			Object instruction = null;
			invocation.setMethodParameters(parameters);

			// before the invocation
			for (int i = 0; i < registeredInterceptors.length; i++) {
				instruction = registeredInterceptors[i].before(invocation);
				if (Boolean.TRUE.equals(instruction)) {
					if (logger.isDebugEnabled()) {
						logger.debug("over interceptor: "
								+ registeredInterceptors[i].getInner()
										.getClass().getName());
					}
					continue;
				} else {
					if (logger.isDebugEnabled()) {
						logger.debug("interceptor has broken "
								+ "the web invocation: "
								+ registeredInterceptors[i].getInner()
										.getClass().getName());
					}
					if (instruction == null
							|| Boolean.FALSE.equals(instruction)) {
						instruction = View.name("break-"
								+ registeredInterceptors[i].getName());
					}
					// ！！！如果拦截器阻止了调用流程，则直接返回！！！
					return instruction;
				}
			}

			// invoke the action

			if (controllerInvocation.isProxiedController()) {
				instruction = Proxy.getInvocationHandler(
						controllerInvocation.getController()).invoke(
						controllerInvocation.getController(), method,
						parameters);
			} else {
				instruction = method.invoke(controllerInvocation
						.getController(), parameters);
			}
			if (instruction == null && method.getReturnType() == void.class) {
				Return returnAnnotation = method.getAnnotation(Return.class);
				if (returnAnnotation != null) {
					instruction = returnAnnotation.value();
				}
			}
			Object orginInstruction = instruction;

			// after the invocation
			for (int i = registeredInterceptors.length - 1; i >= 0; i--) {
				if (registeredInterceptors[i] == null) {
					continue;
				}
				instruction = registeredInterceptors[i].after(invocation,
						instruction);
				// 拦截器返回null的，要恢复为原instruction
				// 这个功能非常有用!!
				if (instruction == null) {
					instruction = orginInstruction;
				}
			}
			return instruction;
		} finally {
			// Clean up any resources used by a multipart request.
			if (processedRequest != request) {
				cleanupMultipart(processedRequest);
			}

		}
	}

	private void applyHttpFeatures(HttpServletRequest request,
			HttpServletResponse response) throws UnsupportedEncodingException {
		HttpFeatures httpFeatures = method.getAnnotation(HttpFeatures.class);
		if (httpFeatures == null) {
			httpFeatures = controllerInvocation.getControllerClass()
					.getAnnotation(HttpFeatures.class);
		}
		if (httpFeatures != null) {
			if (StringUtils.isNotBlank(httpFeatures.contentType())) {
				response.setContentType(httpFeatures.contentType());
				if (logger.isDebugEnabled()) {
					logger.debug("set response.contentType by metadata:"
							+ response.getContentType());
				}
			}
			if (StringUtils.isNotBlank(httpFeatures.charset())) {
				request.setCharacterEncoding(httpFeatures.charset());
				response.setCharacterEncoding(httpFeatures.charset());
				if (logger.isDebugEnabled()) {
					logger.debug("set request/response.characterEncoding "
							+ "by metadata:" + httpFeatures.charset());
				}
			}
		}
		if (response.getContentType() == null) {
			response.setContentType("text/html;charset=UTF-8");
			if (logger.isDebugEnabled()) {
				logger.debug("set response.contentType by default:"
						+ response.getContentType());
			}
		}
		if (request.getCharacterEncoding() == null) {
			request.setCharacterEncoding("UTF-8");
			if (logger.isDebugEnabled()) {
				logger.debug("set request.characterEncoding by default:"
						+ request.getCharacterEncoding());
			}
		}
		if (response.getCharacterEncoding() == null) {
			response.setCharacterEncoding("UTF-8");
			if (logger.isDebugEnabled()) {
				logger.debug("set response.characterEncoding by default:"
						+ response.getCharacterEncoding());
			}
		}
	}

	public Param getParamAnnocationAt(int i) {
		return this.paramAnnotations[i];
	}

	/**
	 * Convert the request into a multipart request, and make multipart resolver
	 * available. If no multipart resolver is set, simply use the existing
	 * request.
	 * 
	 * @param request
	 *            current HTTP request
	 * @return the processed request (multipart wrapper if necessary)
	 * @see MultipartResolver#resolveMultipart
	 */
	protected HttpServletRequest checkMultipart(HttpServletRequest request)
			throws MultipartException {
		if (controllerInvocation.getModule().getMultipartResolver() != null
				&& controllerInvocation.getModule().getMultipartResolver()
						.isMultipart(request)) {
			if (request instanceof MultipartHttpServletRequest) {
				logger.debug("Request is already a "
						+ "MultipartHttpServletRequest");
			} else {
				return controllerInvocation.getModule().getMultipartResolver()
						.resolveMultipart(request);
			}
		}
		// If not returned before: return original request.
		return request;
	}

	/**
	 * Clean up any resources used by the given multipart request (if any).
	 * 
	 * @param request
	 *            current HTTP request
	 * @see MultipartResolver#cleanupMultipart
	 */
	protected void cleanupMultipart(HttpServletRequest request) {
		if (request instanceof MultipartHttpServletRequest) {
			controllerInvocation.getModule().getMultipartResolver()
					.cleanupMultipart((MultipartHttpServletRequest) request);
		}
	}

	
	@Override
	public String toString() {
		if (toStringCached == null) {
			Class<?>[] parameterTypes = method.getParameterTypes();
			String appPackageName = controllerInvocation.getControllerClass().getPackage().getName();
			if (appPackageName.indexOf('.') != -1) {
				appPackageName = appPackageName.substring(0, appPackageName.lastIndexOf('.'));
			}
			String temp = "";
			for (int i = 0; i < parameterTypes.length; i++) {
				if (temp.length() == 0) {
					temp =  showSimpleName(parameterTypes[i], appPackageName);
				} else {
					temp = temp + ", " +  showSimpleName(parameterTypes[i], appPackageName);
				}
			}
			toStringCached = "invocation [controller="
				+ controllerInvocation.getControllerClass().getName() //
				+ "; method=" + method.getName()
				+ "(" + temp + ")"
				+ ":" +  showSimpleName(method.getReturnType(), appPackageName)
				+ "]";
		}
		return toStringCached;
	}

	private String showSimpleName(Class<?> parameterType,
			String appPackageName) {
		if (parameterType.getName().startsWith("net.paoding")
				|| parameterType.getName().startsWith("java.lang")
				|| parameterType.getName().startsWith("java.util")
				|| parameterType.getName().startsWith(appPackageName)){
			return parameterType.getSimpleName();
		}
		return parameterType.getName();
	}

}
